﻿#include "ui/dock_container.h"
#include "context.h"
#include "draw.h"
#include <algorithm>

using namespace std;
using namespace anip;

ui::SplitContainer::SplitContainer(Container* parent, Style s, invec2 pos, invec2 size):
	AutoDockable(parent, pos, size), _style(s)
{
}

void ui::SplitContainer::detachChild(Control& c)
{
	if (_ctrl1 == &c)
	{
		dynamic_cast<AutoDockableBase*>(_ctrl1)->_container = nullptr;
		_ctrl1 = nullptr;
	}
	else if (_ctrl2 == &c)
	{
		dynamic_cast<AutoDockableBase*>(_ctrl2)->_container = nullptr;
		_ctrl2 = nullptr;
	}
	else
		throw invalid_argument("child");
	Control::detachChild(c);
	dynamic_cast<AutoDockableBase&>(c).processOnUndocked();

	if (_firstChild != nullptr && autoDissolve)
		dissolve();
	postSignal({ ui::Signal::Type::NeedsReflow });
}

void ui::SplitContainer::replaceChild(Control& child, Control& repl)
{
	// ANIP_FIXME: This is unsafe and requires dynamic_cast.

	// Note that `(AutoDockableBase*)&repl` is wrong and will screw
	// up basically everything!
	dynamic_cast<AutoDockableBase&>(repl)._container = this;
	if (_ctrl1 == &child)
	{
		dynamic_cast<AutoDockableBase*>(_ctrl1)->_container = nullptr;
		_ctrl1 = &repl;
	}
	else if (_ctrl2 == &child)
	{
		dynamic_cast<AutoDockableBase*>(_ctrl2)->_container = nullptr;
		_ctrl2 = &repl;
	}
	else
		throw invalid_argument("child");
	dynamic_cast<AutoDockableBase&>(child).processOnUndocked();
	dynamic_cast<AutoDockableBase&>(repl).processOnDocked();
	Control::replaceChild(child, repl);

	postSignal({ ui::Signal::Type::NeedsReflow });
}

bool ui::SplitContainer::canBeginDrag(MouseButton btn, invec2 pos)
{
	if (math::inRectangle(pos, _resizerPos, _resizerSize))
	{
		_dragSplitPos = _splitPos;
		return true;
	}
	return false;
}

void ui::SplitContainer::processDrag(invec2 start, invec2 pos)
{
	setSplitPosition(_dragSplitPos + math::dot(pos - start, _splitAxis));
}

void ui::SplitContainer::processEndDrag()
{
}

void ui::SplitContainer::processMouseMove(invec2 pos)
{
	Control::processMouseMove(pos);

	if (math::inRectangle(pos, _resizerPos, _resizerSize))
		Context::setCursor(_style == Style::Horizontal 
			? KnownCursor::HResize 
			: KnownCursor::VResize);
}

void ui::SplitContainer::setSize(invec2 s)
{
	if (s == _size) return;
	vec2 oldsize = _size;
	Control::setSize(s);
	setSplitPosition(_splitPos * math::dot(s / oldsize, _splitAxis));
}

void ui::SplitContainer::setStyle(Style s)
{
	if (s == _style) return;
	_style = s;
	setSplitPosition(_splitPos);
}

void ui::SplitContainer::setSplitPosition(float s)
{
	postSignal({ ui::Signal::Type::NeedsReflow });

	float w = r::SplitResizer_Width;
	switch (_style)
	{
	case Style::Horizontal:
		_splitAxis = { 1, 0 };
		_resizerSize = { w, _size.y };
		break;
	case Style::Vertical:
		_splitAxis = { 0, 1 };
		_resizerSize = { _size.x, w };
		break;
	}
	_splitPos = clamp(s, w, math::dot(_size, _splitAxis) - w);
	_resizerPos = (_splitPos - w * 0.5f) * _splitAxis;
}

void ui::SplitContainer::flowChildren(Flow& _)
{
	if (_ctrl1 != nullptr && _ctrl1->markedDelete())
	{
		Control* c = _ctrl1;
		detachChild(*c);
		delete c;
	}
	if (_ctrl2 != nullptr && _ctrl2->markedDelete())
	{
		Control* c = _ctrl2;
		detachChild(*c);
		delete c;
	}

	vec2 s1{}, p2{}, s2{};
	float w = r::SplitResizer_Width;
	switch (_style)
	{
	case ui::SplitContainer::Style::Horizontal:
		p2 = _resizerPos + vec2{ w, 0 };
		s1 = { _resizerPos.x, _size.y };
		s2 = { _size.x - p2.x, _size.y };
		break;
	case ui::SplitContainer::Style::Vertical:
		p2 = _resizerPos + vec2{ 0, w };
		s1 = { _size.x, _resizerPos.y };
		s2 = { _size.x, _size.y - p2.y };
		break;
	}

	if (_ctrl1 != nullptr)
	{
		Flow f((Container&)(*this));
		f.dockableStart = { 0, 0 };
		f.dockableEnd = s1;
		_ctrl1->setPosition({ 0, 0 });
		_ctrl1->setSize(s1);
		_ctrl1->flow(f);
	}
	if (_ctrl2 != nullptr)
	{
		Flow f((Container&)(*this));
		f.dockableStart = p2;
		f.dockableEnd = p2 + s2;
		_ctrl2->setPosition(p2);
		_ctrl2->setSize(s2);
		_ctrl2->flow(f);
	}
}

void ui::SplitContainer::dissolve()
{
	assert(_firstChild != nullptr && _parent != nullptr);

	Control& ctrl = *_firstChild;
	ctrl.layoutOptions = layoutOptions;
	detachChild(ctrl);
	((Container*)_parent)->replaceChild(*this, ctrl);
	((Container*)ctrl.parent())->flowChildrenDefault();
	postSignal({ ui::Signal::Type::NeedsReflow });

	// remove this
	// ANIP_FIXME: what does this mean?
	((Container*)topContainer())->addChild(*this);
	markDelete(); 
}

void ui::SplitContainer::paint()
{
	Context::noScissor();
	Draw::rect(_resizerPos + _absPos, _resizerSize, { 0, 0, 0, 0.3f });
	Control::paint();
	paintChildren();
}


ui::AutoDockContainer::AutoDockContainer(Container* parent, invec2 pos, invec2 size):
	AutoDockable(parent, pos, size)
{
}

void ui::AutoDockContainer::replaceChild(Control& child, Control& repl)
{
	// we do not permit the user to replace _tab
	if (&child != _central) throw invalid_argument("child");

	_central = &repl;
	dynamic_cast<AutoDockableBase&>(repl)._container = this;
	dynamic_cast<AutoDockableBase&>(child)._container = nullptr;
	Control::replaceChild(child, repl);

	dynamic_cast<AutoDockableBase&>(child).processOnUndocked();
	dynamic_cast<AutoDockableBase&>(repl).processOnDocked();
}

void ui::AutoDockContainer::detachChild(Control& child)
{
	if (_tab == &child)
	{
		_tab = nullptr;
		Control::detachChild(child);
	} else if (_central == &child)
	{
		_central = nullptr;
		dynamic_cast<AutoDockableBase&>(child)._container = nullptr;
		Control::detachChild(child);

		dynamic_cast<AutoDockableBase&>(child).processOnUndocked();

	} else throw invalid_argument("child");
}

void ui::AutoDockContainer::processMouseButton(MouseButton btn, ButtonAction action)
{
	Control::processMouseButton(btn, action);
	if (_parent != topContainer() && _central == nullptr
		&& btn == MouseButton::Middle && action == ButtonAction::Up)
	{
		markDelete();
		postSignal({ ui::Signal::Type::NeedsRedraw });
	}
}

ui::AutoDockStyle ui::AutoDockContainer::setDockHint(vec2 pos)
{
	//  __________
	// | \______/ |
	// | |      | |
	// | |______| |
	// |_/______\_|
	//
	postSignal({ ui::Signal::Type::NeedsRedraw });

	vec2 p = { min(pos.x, _size.x - pos.x), min(pos.y, _size.y - pos.y) };
	if (p.x <= p.y && p.x < _size.x * 0.3)
		_hint = pos.x == p.x ? AutoDockStyle::Left : AutoDockStyle::Right;
	else if (p.x >= p.y && p.y < _size.y * 0.3)
		_hint = pos.y == p.y ? AutoDockStyle::Top : AutoDockStyle::Bottom;
	else 
		_hint = AutoDockStyle::Center;
	return _hint;
}

void ui::AutoDockContainer::noDockHint()
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	_hint = AutoDockStyle::None;
}

void ui::AutoDockContainer::paint()
{
	Control::paint();
	paintChildren();

	color c = r::DockContainer_HintColor;
	switch (_hint)
	{
	case AutoDockStyle::Left:
		Draw::rect(_absPos, _size * vec2{ 0.3f, 1 }, c);
		break;
	case AutoDockStyle::Right:
		Draw::rect(_absPos + vec2{_size.x, 0}, _size * vec2{ -0.3f, 1 }, c);
		break;
	case AutoDockStyle::Top:
		Draw::rect(_absPos, _size * vec2{ 1, 0.3f }, c);
		break;
	case AutoDockStyle::Bottom:
		Draw::rect(_absPos + vec2{ 0, _size.y }, _size * vec2{ 1, -0.3f }, c);
		break;
	case AutoDockStyle::Center:
		Draw::rect(_absPos + _size * 0.3f, _size * 0.4f, c);
		break;
	default:
		break;
	}

	// the hint is temporary -- the control waiting for AutoDock must apply for
	// it every frame.
	_hint = AutoDockStyle::None;
}

void ui::AutoDockContainer::flowChildren(Flow& _)
{
	Control* ctrl = _tab == nullptr ? _central : _tab;
	if (ctrl != nullptr)
	{
		if (ctrl->markedDelete())
		{
			detachChild(*ctrl);
			delete ctrl;
		}
		else
		{
			Flow f((Container&)(*this));
			f.dockableStart = { 0, 0 };
			f.dockableEnd = _size;
			ctrl->setPosition({ 0, 0 });
			ctrl->setSize(_size);
			ctrl->flow(f);
		}
	}
}